package current.limiting.service.impl;

import cn.hutool.core.util.ObjectUtil;
import current.limiting.annotions.Limiting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.concurrent.ConcurrentHashMap;

import static current.limiting.commom.enumerate.DealType.FAST_FAIL;
import static current.limiting.utils.LimitUtil.time2Millisecond;

/**
 * 固定时间优化版本(通过Redis实现) 接口限流策略
 * <p>
 * 该接口从第一次被访问开始计时，在指定的时间段内仅支持最大给定数量的请求访问；当给定的时间段后的第一次访问将会重置计时
 * 优化点：设置每两个请求间的最小时间间隔(时间间隔取值：单位时间段允许通过的数量 / 单位时间 * 3)
 *
 * @author LiKun
 */
@Service
public class FixedTimeRedisCurrentLimitOptimizeService extends FixedTimeRedisCurrentLimitService {
    private static final Logger logger = LoggerFactory.getLogger(FixedTimeRedisCurrentLimitOptimizeService.class);

    /**
     * 存储每个接口上次请求通过的时间点
     */
    private final ConcurrentHashMap<String, Long> recorder = new ConcurrentHashMap<>();

    @Override
    public boolean callBackHandler(String id, String key, long blockTime, Limiting limiting) throws InterruptedException {
        // 每个请求通过的最小间隔时间 = (限流时间段 * 2) / 限流流量    --->    误差更小
        Long accessIntervals = (time2Millisecond(limiting.limitTime(), limiting.timeUnit()) << 1) / limiting.accessQuantity();

        synchronized (id.intern()) {
            Long lastAccessTime = recorder.get(key);                              // 上次通过时间
            long currentTime = System.currentTimeMillis();                        // 记录当前时间(减小误差)
            long currentIntervals = currentTime - (ObjectUtil.isNull(lastAccessTime) ? 0L : lastAccessTime);      // 当前时间点与上次通过请求的时间间隔

            logger.info("线程{}进入回调函数，accessIntervals:{}，currentIntervals：{}", Thread.currentThread().getName(), accessIntervals, currentIntervals);

            // 当前间隔时间 小于 限定的最小间隔时间
            if (currentIntervals < accessIntervals) {
                logger.info("开始判断线程{}是否被允许访问接口，blockTime:{}，needSleepTime：{}", Thread.currentThread().getName(), blockTime, accessIntervals - currentIntervals);
                if (FAST_FAIL == limiting.dealType() || (limiting.blockTime() != 0 && blockTime < accessIntervals - currentIntervals)) {
                    logger.info("线程{}不被允许访问接口(FAST_FAIL.FastFail.Speed)，blockTime:{}，lossTime：{}", Thread.currentThread().getName(), blockTime, accessIntervals - currentIntervals);
                    return false;
                }
                Thread.sleep(accessIntervals - currentIntervals);             // 将剩余的时间在Sleep中度过
                currentTime = lastAccessTime + accessIntervals;                    // 计算本次通过的理论时间(用于保存)
            }

            recorder.put(key, currentTime);                                        // 记录该接口本次请求通过的时间点(近似当前时间)
        }

        logger.info("线程{}被允许访问接口", Thread.currentThread().getName());
        return true;
    }
}
